import { computed, h, onUnmounted, ref, unref, watch } from 'vue';
import { viewImages } from '@gis/lib/components/img-viewer/viewer.js';
import { Button } from 'ant-design-vue';
import {
  DeleteOutlined,
  EyeOutlined,
  LoadingOutlined,
  PlusOutlined,
  UploadOutlined,
  CheckCircleFilled,
} from '@ant-design/icons-vue';
import {
  getAcceptTypes,
  getWrapFile,
  getWrapFileList,
  handleUpload,
  isFileAcceptable,
  isFileExist,
  isImage,
} from './fileUtils.js';

function useFileDialog(options, onChange) {
  options = Object.assign(
    {
      document: null,
      capture: null,
      accept: '*',
      multiple: false,
      directory: false,
    },
    options
  );

  let accept = getAcceptTypes(options.accept).mime;

  function reset(then) {
    let doc = options.document || document;
    if (doc) {
      let input = doc.createElement('input');
      input.type = 'file';
      input.onchange = (e) => {
        typeof onChange === 'function' && onChange(e.target.files);
        input.value = '';
        input = null;
      };
      typeof then === 'function' && then(input);
    }
  }
  function open(_options) {
    reset((input) => {
      _options = Object.assign({}, options, _options);
      input.accept = accept || '*';
      input.capture = _options.capture;
      input.multiple = _options.multiple === true;
      input.webkitdirectory = _options.directory === true;
      input.click();
    });
  }

  function destroy() {
    options = accept = onChange = null;
  }
  return { open, destroy };
}

const upload_picker_style = {
  display: 'flex',
  flexDirection: 'column',
  flexWrap: 'nowrap',
  justifyContent: 'center',
  alignItems: 'center',
  width: '100%',
  height: '100%',
  color: '#aaccee',
  overflow: 'hidden',
};
const upload_list_style = {
  display: 'flex',
  flexDirection: 'row',
  flexWrap: 'wrap',
  justifyContent: 'flex-start',
  alignItems: 'flex-start',
  width: '100%',
  height: '100%',
  overflow: 'hidden',
};
const upload_border_style = {
  padding: '0.5em',
  backgroundColor: 'rgba(0,0,0,0.1)',
  border: '1px dashed #1890ff',
  borderRadius: '4px',
};
const upload_file_wrap_style = {
  position: 'relative',
  boxSizing: 'border-box',
  width: '100%',
  height: '100%',
};
const upload_mask_style = {
  position: 'absolute',
  top: 0,
  right: 0,
  bottom: 0,
  left: 0,
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
  gap: '14px',
  background: 'rgba(0, 0, 0, 0.5)',
  color: 'var(--font-color-2)',
  transition: 'opacity 0.3s',
};
const upload_ellipsis_style = {
  width: '100%',
  whiteSpace: 'nowrap',
  overflow: 'hidden',
  textOverflow: 'ellipsis',
};

const IconBtn = {
  setup(props, { slots }) {
    let children = Object.keys(slots).reduce((res, key) => {
      res[key] = () => {
        let list = slots[key]();
        if (list && list.length) {
          list = list.map((item) => h(item, { style: { fontSize: 'inherit' } }));
        }
        return list;
      };
      return res;
    }, {});
    let style = { width: 'unset', height: 'unset', padding: 0, border: 'none', background: 'none', fontSize: '18px' };
    return () => h(Button, { style }, children);
  },
};
const Preview = {
  props: {
    file: undefined,
    getter: undefined,
    showName: undefined,
    border: undefined,
    delEnable: undefined,
    previewEnable: undefined,
  },
  emits: ['del'],
  setup(props, { emit }) {
    let show = ref(false);
    let file = computed(() => getWrapFile(props.file, props.getter));
    let url = computed(() => unref(file.value.thumbUrl || file.value.url) || '');
    let name = computed(() => unref(file.value.name) || '');

    function preview() {
      viewImages(url.value);
    }
    function renderFileLoading() {
      return !url.value ? h('div', { style: { ...upload_mask_style } }, h(LoadingOutlined, { spin: true })) : null;
    }
    function renderImg() {
      return h('img', { src: url.value, style: { width: '100%', height: '100%', objectFit: 'cover' } });
    }
    function renderName() {
      if (!name.value || props.showName !== true) {
        return null;
      }
      return h('div', { style: { ...upload_ellipsis_style, padding: '4px', textAlign: 'center' } }, name.value);
    }
    function renderPreviewMask() {
      return h('div', { style: { ...upload_mask_style, opacity: show.value ? 1 : 0 } }, [
        props.previewEnable !== false && isImage(file.value)
          ? h(IconBtn, { onClick: (e) => preview() }, { icon: () => h(EyeOutlined) })
          : null,
        props.delEnable !== false
          ? h(IconBtn, { onClick: (e) => emit('del', file.value) }, { icon: () => h(DeleteOutlined) })
          : null,
      ]);
    }
    function renderPreview() {
      return h('div', { style: { ...upload_file_wrap_style } }, [
        renderImg(),
        renderPreviewMask(),
        renderFileLoading(),
      ]);
    }
    onUnmounted(() => {
      props = emit = show = file = url = name = null;
    });
    return () =>
      file.value
        ? h(
            'div',
            {
              style: { ...upload_file_wrap_style },
              onMouseenter: () => (show.value = true),
              onMouseleave: () => (show.value = false),
            },
            [renderPreview(), renderName()]
          )
        : null;
  },
};
const PreviewList = {
  props: {
    fileList: undefined,
    getter: undefined,
    gutter: undefined,
    size: undefined,
    delEnable: undefined,
    previewEnable: undefined,
  },
  setup(props, { emit }) {
    let onDel = (e) => emit('del', e);
    let size = props.size || '100px';
    let style = { ...upload_border_style, width: size, height: size };
    let dataList = computed(() => {
      if (!props.fileList || !props.fileList.length) {
        return [];
      }
      return getWrapFileList(props.fileList, props.getter);
    });
    onUnmounted(() => {
      props = emit = size = style = dataList = onDel = null;
    });
    return () =>
      dataList.value && dataList.value.length
        ? h(
            'div',
            { style: { ...upload_list_style, gap: props.gutter || '8px' } },
            dataList.value.map((file) => h(Preview, { ...props, file, style, showName: true, onDel }))
          )
        : null;
  },
};
const Card = {
  props: {
    loading: undefined,
    fileList: undefined,
    placeholder: undefined,
    delEnable: undefined,
    previewEnable: undefined,
    pickerEnable: undefined,
  },
  emits: ['del', 'open'],
  setup(props, { attrs, slots, emit }) {
    let onDel = (e) => emit('del', e);
    let file = computed(() => {
      if (props.fileList && props.fileList.length) {
        return props.fileList[props.fileList.length - 1];
      }
    });

    function renderLoading() {
      return h('div', { style: { ...upload_picker_style } }, h(LoadingOutlined, { spin: true }));
    }
    function renderPicker() {
      return h(
        'div',
        {
          style: { ...upload_picker_style, cursor: 'pointer' },
          onClick: (e) => emit('open', { multiple: false }),
        },
        slots.default
          ? slots.default({ loading: props.loading })
          : [
              h(PlusOutlined, { style: { fontSize: '24px', color: 'currentColor' } }),
              h('div', { style: { fontSize: '1em' } }, props.placeholder || '选择文件'),
            ]
      );
    }
    onUnmounted(() => {
      props = attrs = slots = emit = file = onDel = null;
    });
    return () =>
      h(
        'div',
        { style: { ...upload_border_style, width: '100%', height: '100%' } },
        props.loading
          ? renderLoading()
          : file.value
          ? h(Preview, { ...props, onDel, file: file.value })
          : props.pickerEnable !== false
          ? renderPicker()
          : null
      );
  },
};
const CardList = {
  props: {
    loading: undefined,
    fileList: undefined,
    buttonProps: undefined,
    placeholder: undefined,
    delEnable: undefined,
    previewEnable: undefined,
    pickerEnable: undefined,
  },
  emits: ['del', 'open'],
  setup(props, { attrs, slots, emit }) {
    let onDel = (e) => emit('del', e);
    function renderPicker() {
      return h(
        'div',
        { onClick: (e) => emit('open') },
        slots.default
          ? slots.default({ loading: props.loading })
          : h(Button, Object.assign({}, props.buttonProps, { loading: props.loading ? { delay: 50 } : false }), () => [
              h(PlusOutlined),
              h('span', null, props.placeholder || '选择文件'),
            ])
      );
    }
    onUnmounted(() => {
      props = attrs = slots = emit = onDel = null;
    });
    return () => {
      h('div', { style: {} }, [
        props.pickerEnable !== false ? renderPicker() : null,
        h(PreviewList, { ...props, onDel }),
      ]);
    };
  },
};
const NormalList = {
  props: {
    loading: undefined,
    fileList: undefined,
    maxCount: undefined,
    buttonProps: undefined,
    placeholder: undefined,
    nameStyle: undefined,
    showImmediateBtn: undefined,
    autoHideUploadBtn: undefined,
  },
  emits: ['del', 'open', 'upload'],
  setup(props, { emit, slots }) {
    function renderFileName(file) {
      file = unref(file);
      let name = computed(() => (file ? unref(file.name) : ''));
      let url = computed(() => (file ? unref(file.url) : ''));
      let done = computed(() => (file ? unref(file.url) : false));
      let style = computed(() =>
        Object.assign({ flex: 'auto', padding: '0 0.5em' }, upload_ellipsis_style, props.nameStyle, {
          color: !!done.value ? '#1890ff' : null,
        })
      );
      return h(
        'div',
        {
          style: { display: 'flex', flexWrap: 'nowrap', alignItems: 'center', width: '100%', padding: '4px 0' },
          onVnodeBeforeUnmount: () => (file = name = done = style = null),
        },
        [
          h('div', { style: style.value, title: name.value }, [
            !!done.value ? h(CheckCircleFilled, { style: { color: '#1890ff', margin: '0 0.5em 0 0' } }) : null,
            url.value ? h('a', { href: url.value, download: name.value, target: '_blank' }, name.value) : name.value,
          ]),
          h(IconBtn, { onClick: () => emit('del', file) }, { icon: () => h(DeleteOutlined) }),
        ]
      );
    }
    function renderPickerBtn() {
      return h(
        'div',
        { onClick: (e) => emit('open') },
        slots.default
          ? slots.default({ loading: props.loading })
          : h(Button, Object.assign({}, props.buttonProps, { loading: props.loading ? { delay: 50 } : false }), () => [
              h(PlusOutlined),
              h('span', null, props.placeholder || '选择文件'),
            ])
      );
    }
    function renderUploadBtn() {
      if (props.showImmediateBtn !== true) {
        return null;
      }
      return h(
        Button,
        Object.assign({}, props.buttonProps, {
          loading: props.loading ? { delay: 50 } : false,
          onClick: (e) => emit('upload'),
        }),
        () => [h(UploadOutlined), h('span', null, '立即上传')]
      );
    }
    function renderFileList() {
      if (!props.fileList || !props.fileList.length) {
        return null;
      }
      let style = { width: '100%', boxSizing: 'border-box' };
      return h('div', { style }, props.fileList.map(renderFileName));
    }
    function renderBtn() {
      if (props.autoHideUploadBtn === true && props.fileList && props.fileList.length >= props.maxCount) {
        return null;
      }
      return h('div', { style: { display: 'inline-flex', gap: '12px' } }, [renderPickerBtn(), renderUploadBtn()]);
    }

    onUnmounted(() => {
      props = emit = slots = null;
    });

    return () => {
      return h('div', { style: { width: '100%' } }, [renderBtn(), renderFileList()]);
    };
  },
};

const Uploader = {
  name: 'Uploader',
  props: {
    modelValue: undefined,
    names: undefined,
    urls: undefined,
    accept: undefined,
    multiple: undefined,
    directory: undefined,
    maxCount: undefined,
    autoUpdate: undefined,
    viewStyle: undefined,
    useSubmitHook: undefined,
  },
  emits: ['update:modelValue', 'update:urls', 'update:names', 'registerSubmitHook'],
  setup(props, { attrs, slots, emit, expose }) {
    let loading = ref(false);
    let fileList = ref([]);
    let maxCount = Number(props.maxCount) || 9;

    let { open, destroy } = useFileDialog(props, async (files) => {
      loading.value = true;
      await addFiles(files).catch(() => {
        loading.value = false;
      });
      loading.value = false;
    });

    function onOpen() {
      if (loading.value !== true) open();
    }
    async function addFiles(files) {
      await Promise.all(
        Array.prototype.map.call(files, async (file) => {
          file = getWrapFile(file);
          if (isFileExist(file, fileList.value) || !isFileAcceptable(file, props.accept)) {
            return Promise.reject(false);
          }
          let index = fileList.value.length < maxCount ? 0 : 1;
          let list = fileList.value.slice(index);
          fileList.value = [...list, file];
          if (props.autoUpdate === true) {
            await handleUpload(file);
          }
        })
      );
      output();
    }
    function delFile(file) {
      let inx = fileList.value.indexOf(file);
      if (inx >= 0) {
        fileList.value.splice(inx, 1);
        output();
      }
    }
    function output(useEmit) {
      let names = [];
      let urls = [];
      fileList.value.forEach((file) => {
        if (file && file.url) {
          names.push(file.name);
          urls.push(file.url);
        }
      });
      if (useEmit !== false) {
        emit('update:names', names.join(','));
        emit('update:urls', urls.join(','));
        emit('update:modelValue', fileList.value);
      }
      return fileList.value;
    }
    async function upload() {
      loading.value = true;
      await Promise.all(
        fileList.value.map(async (file) => {
          await handleUpload(file);
        })
      );
      loading.value = false;
      return output();
    }

    if (props.useSubmitHook !== false) {
      emit('registerSubmitHook', upload);
    }

    let fileValues = computed(() => {
      let values = [];
      let urls = unref(props.urls);
      let names = unref(props.names);
      let mValue = unref(props.modelValue);
      if (!Array.isArray(names)) {
        names = [];
      }
      if (typeof urls === 'string' && urls) {
        values = getWrapFileList(urls, (n, i) => names[i]);
      } else if (typeof mValue === 'string' && mValue) {
        values = getWrapFileList(mValue, (n, i) => names[i]);
      } else if (Array.isArray(mValue) && mValue.length) {
        values = mValue;
      }
      return values;
    });

    watch(
      fileValues,
      (value) => {
        if (Array.isArray(value) && value !== fileList.value) {
          fileList.value = value;
        }
      },
      { immediate: true }
    );

    onUnmounted(() => {
      destroy();
      props = attrs = slots = emit = loading = fileList = maxCount = fileValues = open = destroy = null;
    });

    expose({ upload, output });

    return () => {
      let view = props.viewStyle === 'card' ? Card : NormalList;
      return h(
        view,
        {
          fileList: fileList.value,
          loading: loading.value,
          maxCount: maxCount,
          onOpen: onOpen,
          onDel: delFile,
          onUpload: upload,
        },
        slots
      );
    };
  },
};

export { Uploader as default, Card, CardList, NormalList, Preview, PreviewList, IconBtn };
